package com.yami.shop.config;

import cn.hutool.core.util.StrUtil;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.unionpay.UnionPayApi;
import com.ijpay.unionpay.enums.ServiceEnum;
import com.ijpay.unionpay.model.RefundModel;
import com.paypal.api.payments.Error;
import com.paypal.api.payments.*;
import com.paypal.base.Constants;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.OAuthTokenCredential;
import com.paypal.base.rest.PayPalRESTException;
import com.paypal.core.PayPalEnvironment;
import com.paypal.core.PayPalHttpClient;
import com.paypal.http.HttpResponse;
import com.paypal.orders.Capture;
import com.paypal.orders.Order;
import com.paypal.orders.Refund;
import com.paypal.orders.*;
import com.paypal.payments.CapturesGetRequest;
import com.paypal.payments.CapturesRefundRequest;
import com.paypal.payments.RefundRequest;
import com.yami.shop.bean.bo.PayInfoResultBO;
import com.yami.shop.bean.param.PayPalOrderParam;
import com.yami.shop.bean.pay.PayInfoDto;
import com.yami.shop.bean.pay.RefundInfoDto;
import com.yami.shop.common.bean.ExchangeRate;
import com.yami.shop.common.bean.PayPal;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.config.WebhookConstant;
import com.yami.shop.common.constants.PaypalConstant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.Arith;
import com.yami.shop.common.util.Json;
import com.yami.shop.service.SysConfigService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.*;

/**
 * 通过微信配置获取微信的支付信息，登陆信息等
 * @author cl
 */
@Slf4j
@Component
@AllArgsConstructor
public class PayPalConfig {

    private final ShopConfig shopConfig;
    private final SysConfigService sysConfigService;

    private static final String PP_SUCCESS = "success";

    public Map<String, String> payPalSdkConfig(){
        Map<String, String> sdkConfig = new HashMap<>(16);
        PayPal payPal = shopConfig.getPayPal();
        sdkConfig.put("mode", payPal.getMode());
        return sdkConfig;
    }

    public OAuthTokenCredential authTokenCredential(){
        PayPal payPal = shopConfig.getPayPal();
        return new OAuthTokenCredential(payPal.getClientId(), payPal.getClientSecret(), payPalSdkConfig());
    }

    public String getPayPalToken() {
        String accessToken = "";
        try {
            // Bearer + xxx
            accessToken = authTokenCredential().getAccessToken();
        } catch (PayPalRESTException e) {
            log.error("PayPalRESTException:", e);
            return accessToken;
        }
        return accessToken;
    }

    public APIContext apiContext() {
        PayPal payPal = shopConfig.getPayPal();
        APIContext apiContext = new APIContext(payPal.getClientId(), payPal.getClientSecret(),payPal.getMode());
        return apiContext;
    }

    public PayPalHttpClient client() {
        PayPal payPal = shopConfig.getPayPal();
        String mode = payPal.getMode();
        String clientId = payPal.getClientId();
        String clientSecret = payPal.getClientSecret();
        PayPalEnvironment environment = StrUtil.equals("live",mode) ?
                new PayPalEnvironment.Live(clientId, clientSecret) :
                new PayPalEnvironment.Sandbox(clientId, clientSecret);
        return new PayPalHttpClient(environment);
    }

    /**
     * 生成paypal支付订单
     * 回调的页面 /pages/orderList/orderList?sts=0
     */
    public OrdersCreateRequest createPayPalOrder(PayInfoDto payInfo) {
        OrdersCreateRequest request = new OrdersCreateRequest();
        request.header("Content-Type","application/json");
        OrderRequest orderRequest = new OrderRequest();
        orderRequest.checkoutPaymentIntent(PaypalConstant.CAPTURE);
        com.paypal.orders.ApplicationContext payPalApplicationContext = new com.paypal.orders.ApplicationContext()
                .brandName("Shopping")
                .landingPage(PaypalConstant.LANDINGPAGE)
                .cancelUrl(payInfo.getReturnUrl())
                .returnUrl(payInfo.getReturnUrl())
                .userAction(PaypalConstant.USERACTION)
                ;
        orderRequest.applicationContext(payPalApplicationContext);
        List<PurchaseUnitRequest> purchaseUnitRequests = new ArrayList<>();
        // 金额转换为美元，然后再转为字符串类型
        // 获取汇率
        ExchangeRate exchangeRate = shopConfig.getExchangeRate();
        BigDecimal amount = new BigDecimal(payInfo.getPayAmount().toString()).multiply(exchangeRate.getUsdExchangeRate()).setScale(2, RoundingMode.HALF_EVEN);
        BigDecimal min = new BigDecimal("0.01");
        // paypal只支持小数后两位，最小必须支付0.01美元
        if (min.compareTo(amount)>=0) {
            amount = min;
        }
        log.info("paypalAmount:{}", amount);
        PurchaseUnitRequest purchaseUnitRequest = new PurchaseUnitRequest()
                .description("Shopping")
                .customId(payInfo.getPayNo())
                .invoiceId(payInfo.getPayNo())
                .amountWithBreakdown(new AmountWithBreakdown()
                        .currencyCode(PaypalConstant.CURRENTCY)
                        // value = itemTotal + shipping + handling + taxTotal + shippingDiscount
                        .value(amount.toString())
                );
        purchaseUnitRequests.add(purchaseUnitRequest);
        orderRequest.purchaseUnits(purchaseUnitRequests);
        request.requestBody(orderRequest);
        return request;
    }

    /**
     * 创建订单
     */
    public PayPalOrderParam getExcetuteHref (OrdersCreateRequest request) {
        HttpResponse<Order> response;
        PayPalOrderParam param = new PayPalOrderParam();
        try {
            response = client().execute(request);
        } catch (IOException e) {
            log.error("调用paypal订单创建失败，失败原因：{}", e.getMessage());
            throw new YamiShopBindException("paypal请求支付失败");
        }
        if (response.statusCode() == 201) {
            param.setOrderId(response.result().id());
            log.info("Status Code = {}, Status = {}, OrderID = {}, Intent = {}", response.statusCode(), response.result().status(), response.result().id(), response.result().checkoutPaymentIntent());
            for (LinkDescription link : response.result().links()) {
                log.info("Links-{}: {}    \tCall Type: {}", link.rel(), link.href(), link.method());
                if("approve".equals(link.rel())) {
                    param.setHref(link.href());
                }
            }
        }

        return param;
    }


    /**
     * 用户授权支付成功，进行扣款操作
     * 用户通过CreateOrder生成 approveUrl 跳转paypal支付成功后，只是授权，并没有将用户的钱打入我们的paypal账户，
     * 我们需要通过 CaptureOrder接口，将钱打入我的PayPal账户
     */
    public PayInfoResultBO captureOrder(String token){
        PayInfoResultBO payInfoResultBO = new PayInfoResultBO();
        OrdersCaptureRequest request = new OrdersCaptureRequest(token);
        request.requestBody(new OrderRequest());
        HttpResponse<Order> response = null;
        try {
            response = client().execute(request);
        } catch (IOException e1) {
            log.error("调用paypal扣款失败，失败原因 {}", e1.getMessage() );
            throw new YamiShopBindException("failure");
        }
        payInfoResultBO.setBizOrderNo(response.result().id());
        log.info("Status Code = {}, Status = {}, OrderID = {}", response.statusCode(), response.result().status(), response.result().id());
        for (LinkDescription link : response.result().links()) {
            log.info("Links-{}: {}    \tCall Type: {}", link.rel(), link.href(), link.method());
        }
        for (PurchaseUnit purchaseUnit : response.result().purchaseUnits()) {
            for (Capture capture : purchaseUnit.payments().captures()) {
                log.info("Capture id: {}", capture.id());
                log.info("status: {}", capture.status());
                log.info("invoice_id: {}", capture.invoiceId());
                //paypal交易号
                payInfoResultBO.setBizPayNo(capture.id());
                //商户订单号,之前生成的带用户ID的订单号
                payInfoResultBO.setPayNo(capture.invoiceId());
                payInfoResultBO.setIsPaySuccess(false);
                ExchangeRate exchangeRate = shopConfig.getExchangeRate();
                BigDecimal payAmount = new BigDecimal(capture.amount().value()).multiply(exchangeRate.getCurExchangeRate()).setScale(2, RoundingMode.HALF_EVEN);
                BigDecimal min = new BigDecimal("0.01");
                // 最小支付金额
                if (min.compareTo(payAmount) >= 0) {
                    payAmount = min;
                }
                log.info("amount: {}", payAmount.doubleValue());
                if("COMPLETED".equals(capture.status())) {
                    //进行数据库操作，修改订单状态为已支付成功，尽快发货（配合回调和CapturesGet查询确定成功）
                    log.info("支付成功,状态为=COMPLETED");
                    payInfoResultBO.setIsPaySuccess(true);
                    payInfoResultBO.setPayAmount(payAmount.doubleValue());
                    payInfoResultBO.setSuccessString(PP_SUCCESS);
                }
                if("PENDING".equals(capture.status())) {
                    log.info("status_details: {}", capture.captureStatusDetails().reason());
                    String reason = "PENDING";
                    if(capture.captureStatusDetails() != null && capture.captureStatusDetails().reason() != null) {
                        reason = capture.captureStatusDetails().reason();
                    }
                    //进行数据库操作，修改订单状态为已支付成功，但触发了人工审核，请审核通过后再发货（配合回调和CapturesGet查询确定成功）
                    log.info("支付成功,状态为=PENDING : {}", reason);
                    payInfoResultBO.setIsPaySuccess(true);
                    payInfoResultBO.setPayAmount(payAmount.doubleValue());
                    payInfoResultBO.setSuccessString(PP_SUCCESS);
                }
            }
        }
//        Payer buyer = response.result().payer()

        return payInfoResultBO;
    }

    /** 查询订单信息 */
    public void queryOrder(String orderId){
        OrdersGetRequest request = new OrdersGetRequest(orderId);
        HttpResponse<Order> response = null;
        try {
            response = client().execute(request);
        } catch (Exception e) {
            System.out.println("调用paypal订单查询失败");
            System.out.println(e.getMessage());
        }
        String intent = response.result().checkoutPaymentIntent();
        String status = response.result().status();
        log.info("Status Code: {}", response.statusCode());
        log.info("Order id: {}", response.result().id());
        log.info("intent: {}", intent);
        log.info("status: {}", status);
        if(response.result().purchaseUnits().get(0).payments() != null) {
            List<Capture> captures = response.result().purchaseUnits().get(0).payments().captures();
            if(captures != null) {
                for (Capture capture : captures) {
                    System.out.println("\t订单编号= " + capture.invoiceId() + "\tCapture Id= " + capture.id() + "\tCapture status= " + capture.status() + "\tCapture amount= " + capture.amount().currencyCode() + ":" + capture.amount().value());
                }
            }
            List<Refund> refunds = response.result().purchaseUnits().get(0).payments().refunds();
            if(refunds != null) {
                for (Refund refund : refunds) {
                    System.out.println("\t售后编号= " + refund.invoiceId() + "\tRefund Id= " + refund.id() + "\tRefund status= " + refund.status() + "\tRefund amount= " + refund.amount().currencyCode() + ":" + refund.amount().value());
                }
            }
        }
        System.out.println("Links: ");
        for (com.paypal.orders.LinkDescription link : response.result().links()) {
            System.out.println("\t" + link.rel() + ": " + link.href() + "\tCall Type: " + link.method());
        }
    }


    /**
     * 申请退款
     */
    public Boolean refundOrder(RefundInfoDto refundInfo){
        // 第三方paypal的交易号
        String captureId = refundInfo.getBizPayNo();
        // payNo 本系统生成的流水号
        String payNo = refundInfo.getPayNo();
        CapturesRefundRequest request = new CapturesRefundRequest(captureId);
        request.prefer("return=representation");
        RefundRequest refundRequest = new RefundRequest();
        com.paypal.payments.Money money = new com.paypal.payments.Money();
        money.currencyCode(PaypalConstant.CURRENTCY);
        // 金额转换为美元，然后再转为字符串类型
        // 获取汇率
        ExchangeRate exchangeRate = shopConfig.getExchangeRate();
        BigDecimal refundAmount = new BigDecimal(refundInfo.getRefundAmount().toString()).multiply(exchangeRate.getUsdExchangeRate()).setScale(2, RoundingMode.HALF_EVEN);
        BigDecimal min = new BigDecimal("0.01");
        // paypal只支持小数后两位，最小必须支付0.01美元
        if (min.compareTo(refundAmount)>=0) {
            refundAmount = min;
        }
        money.value(refundAmount.toString());
        refundRequest.amount(money);
        refundRequest.invoiceId(payNo);
        refundRequest.noteToPayer("7天无理由退款");

        request.requestBody(refundRequest);
        HttpResponse<com.paypal.payments.Refund> response = null;
        try {
            response =client().execute(request);
        } catch (IOException e) {
            log.error("调用paypal退款申请失败，失败原因 {}", e.getMessage());
            return false;
        }
        log.info("Status Code = {}, Status = {}, RefundID = {}", response.statusCode(), response.result().status(), response.result().id());
        if(StrUtil.equals(PaypalConstant.COMPLETED,response.result().status())) {
            // 退款成功
            log.info("退款成功");
            return true;
        }
        return false;

    }

    /**
     * unionPay申请退款
     */
    public Boolean unionPayRefundOrder(RefundInfoDto refundInfo){
        // 第三方平台交易号
        String captureId = refundInfo.getBizPayNo();
        // 订单金额
        double totalFee = Arith.mul(refundInfo.getPayAmount(), 100);
        // 退款金额
        double refundFee = Arith.mul(refundInfo.getRefundAmount(), 100);
        Map<String, String> params = RefundModel.builder()
                .service(ServiceEnum.REFUND.toString())
                .mch_id(shopConfig.getUnionPay().getMachId())
                .out_trade_no(refundInfo.getPayNo())
                .transaction_id(refundInfo.getBizPayNo())
                .out_refund_no(refundInfo.getRefundSn())
                .total_fee(String.valueOf(totalFee))
                .refund_fee(String.valueOf(refundFee))
                .op_user_id(shopConfig.getUnionPay().getMachId())
                .nonce_str(WxPayKit.generateStr())
                .build()
                .createSign(shopConfig.getUnionPay().getKey(), SignType.MD5);
        String xmlResult = UnionPayApi.execution(shopConfig.getUnionPay().getServerUrl(), params);
        log.info("xmlResult:" + xmlResult);
        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
        String status = params.get("status");
        String returnCode = params.get("result_code");
        if ("0".equals(status) && "0".equals(returnCode)) {
            log.info("退款成功");
            return true;

        }else{
            log.error("调用unionPay申请退款，失败原因 {}", params.get("message"));
            return false;
        }
    }

    /**
     * 支付后查询扣款信息
     */
    public void getCapture(PayInfoResultBO payInfo) {
        // 扣款查询
        CapturesGetRequest restRequest = new CapturesGetRequest(payInfo.getBizPayNo());
        HttpResponse<com.paypal.payments.Capture> response = null;
        try {
            response = client().execute(restRequest);
            log.info("Paypal查单：Status Code = {}, Status = {}, RefundID = {}", response.statusCode(), response.result().status(), response.result().id());
            payInfo.setIsPaySuccess("COMPLETED".equals(response.result().status()));
        } catch (IOException e) {
            log.info("查询支付扣款失败：{}",e.getMessage());
            payInfo.setIsPaySuccess(false);
        }
//        log.info("Capture ids: " + response.result().id());
    }

    /**
     * payPal webHook 钩子函数验签
     */
    public boolean verifyWebhookSign(HttpServletRequest request,Map<String, String> headers,String body) {
        try {
            APIContext apiContext = apiContext();
            // Set the webhookId that you received when you created this webhook.
            apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, shopConfig.getPayPal().getWebHookId());
            Boolean result = Event.validateReceivedEvent(apiContext, headers, body);
            return result;
        } catch (PayPalRESTException e) {
            log.error("PayPalRESTException", e);
        } catch (InvalidKeyException e) {
            log.error("InvalidKeyException", e);
        } catch (NoSuchAlgorithmException e) {
            log.error("NoSuchAlgorithmException", e);
        } catch (SignatureException e) {
            log.error("SignatureException", e);
        }
        return false;
    }

    /**
     * 获取请求头部信息
     */
    public Map < String, String > getHeadersInfo(HttpServletRequest request) {
        Map < String, String > map = new HashMap < String, String > (16);
        @SuppressWarnings("rawtypes")
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    /**
     * 创建钩子，回调时用到
     * @param url
     * @return
     */
    public void createWebhook(String url){
        // event types
        List<EventType> eventTypes = eventTypes();
        Webhook webhook = new Webhook();
//        webhook.setId(webHookId)
        // 事件回调地址
        webhook.setUrl(url);
        webhook.setEventTypes(eventTypes);
        try{
            createWebHookRequest(webhook);
            return;
        } catch (PayPalRESTException e) {
            Error details = e.getDetails();
            String name = details.getName();
            if (StrUtil.equals(name,PaypalConstant.WEBHOOK_NUMBER_LIMIT_EXCEEDED)) {
                // 钩子超出限制，查出所有钩子，并且删除
                // 查出所有钩子
                List<Webhook> webHooks = getWebHooks();
                // 批量删除钩子
                deleteWebHooks(webHooks);
                // 重新请求创建钩子
                try {
                    createWebHookRequest(webhook);
                } catch (PayPalRESTException e1) {
                    e1.printStackTrace();
                   log.error("删除所有网路钩子,重新请求钩失败，失败原因： {}",e1.getDetails());
                   return;
                }
            }
            log.error("PayPalRESTException:", e);
        }
        return;
    }

    /**
     * 初始化所有监听事件
     * @return
     */
    private List<EventType> eventTypes() {
        // event type
        List<String> events = new ArrayList<>();
        events.add(WebhookConstant.CHECKOUT_ORDER_APPROVED);
        events.add(WebhookConstant.CHECKOUT_ORDER_COMPLETED);
        events.add(WebhookConstant.CUSTOMER_PAYOUT_COMPLETED);
        events.add(WebhookConstant.PAYMENT_CAPTURE_COMPLETED);
        events.add(WebhookConstant.PAYMENT_SALE_COMPLETED);
        // event types
        List<EventType> eventTypes = new ArrayList();
        for (String event : events) {
            EventType eventType = new EventType();
            eventType.setName(event);
            eventTypes.add(eventType);
        }
        return eventTypes;
    }

    /**
     * 创建钩子
     * @param webhook
     * @throws PayPalRESTException
     */
    private void createWebHookRequest(Webhook webhook) throws PayPalRESTException {
        Webhook createdWebHook = webhook.create(apiContext(), webhook);
        System.out.println("WebHook successfully created with ID " + createdWebHook.getId());
        String webHookId = createdWebHook.getId();
        // 并且将钩子id更新到后台配置中
        // 到创建钩子这步，paypal可定是已经配置好了的，所以我们直接更新就好了
        if (StrUtil.isBlank(webHookId)) {
            return;
        }
        PayPal payPal = shopConfig.getPayPal();
        payPal.setWebHookId(webHookId);
        sysConfigService.updateValueByKey(Constant.PAYPAL_CONFIG, Json.toJsonString(payPal));
    }

    /**
     * 获取所有钩子
     * @return
     */
    private List<Webhook> getWebHooks(){
        WebhookList webhookList = new WebhookList();
        try {
            webhookList = webhookList.getAll(apiContext());
            List<Webhook> webhooks = webhookList.getWebhooks();
            return webhooks;
        } catch (PayPalRESTException e) {
            log.error("PayPalRESTException:", e);
        }
        return null;
    }

    /**
     * 批量删除网络钩子
     * @param webhooks
     */
    private void deleteWebHooks(List<Webhook> webhooks){
        if (CollectionUtils.isEmpty(webhooks)) {
            return;
        }
        for (Webhook webhook : webhooks) {
            deleteWebHook(webhook);
        }
    }

    /**
     * 删除单个网络钩子
     * @param webhook
     */
    private void deleteWebHook(Webhook webhook){
        String id = webhook.getId();
        try {
            webhook.delete(apiContext(),id);
        } catch (PayPalRESTException e) {
            log.error("PayPalRESTException:", e);
        }
    }
}
